﻿#region License, Terms and Conditions
//
// Jayrock - JSON and JSON-RPC for Microsoft .NET Framework and Mono
// Written by Atif Aziz (www.raboof.com)
// Copyright (c) 2005 Atif Aziz. All rights reserved.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
//
#endregion

namespace Jayrock.Json.Conversion.Generation
{
#if !NET_1_0 && !NET_1_1 && !NET_2_0

    #region Imports

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;

    #endregion

    public delegate T JsonImporter<out T>(JsonReader reader);
    public delegate T JsonImportAutomaton<out T>(ImportState state);
    public delegate T JsonMemberImportAutomaton<T>(ImportState state, T container);

    public static class JsonImport
    {
        public static JsonImporter<T> Value<T>(this JsonImportAutomaton<T> automaton)
        {
            return automaton.ValueOrError(v => v, e => { throw e; });
        }

        public static JsonImporter<TResult> ValueOrError<T, TResult>(
            this JsonImportAutomaton<T> automaton, 
            Func<T, TResult> valueSelector, 
            Func<Exception, TResult> errorSelector)
        {
            if (automaton == null) throw new ArgumentNullException("automaton");
            if (valueSelector == null) throw new ArgumentNullException("valueSelector");
            if (errorSelector == null) throw new ArgumentNullException("errorSelector");

            return reader =>
            {
                var state = ImportState.Create(reader);
                var value = automaton(state);
                return state.Success
                     ? valueSelector(value)
                     : errorSelector(state.Error);
            };
        }

        public static JsonImportAutomaton<JsonToken> Content()
        {
            return state => state.Input.MoveToContent()
                          ? state.Succeeding(state.Input.Token)
                          : state.Failing<JsonToken>();
        }

        public static JsonImportAutomaton<TResult> Then<T, TResult>(this JsonImportAutomaton<T> first, Func<T, JsonImportAutomaton<TResult>> second)
        {
            return state =>
            {
                var a = first(state);
                return !state.Success 
                     ? default(TResult)
                     : second(a)(state);
            };
        }

        internal static JsonImportAutomaton<T> ContentThen<T>(JsonImportAutomaton<T> automaton)
        {
            return Content().Then(_ => automaton);
        }

        public static JsonImportAutomaton<T> Return<T>(T value)
        {
            return state => state.Succeeding(value);
        }

        public static JsonImportAutomaton<T> ReturnError<T>()
        {
            return ReturnError<T>(null);
        }

        public static JsonImportAutomaton<T> ReturnError<T>(Exception e)
        {
            return state => state.Failing<T>(e);
        }

        public static JsonImportAutomaton<T> Const<T>(T value)
        {
            return ContentThen(state =>
            {
                state.Input.Skip();
                return state.Succeeding(value);
            });
        }

        static JsonImportAutomaton<T> Scalar<T>(JsonTokenClass tokenClass, Func<ImportState, T> readFunc)
        {
            Debug.Assert(tokenClass != null);
            Debug.Assert(tokenClass.IsScalar || tokenClass == JsonTokenClass.Null);
            Debug.Assert(readFunc != null);

            return ContentThen(state => state.Input.TokenClass == tokenClass 
                                      ? state.Succeeding(readFunc(state)) 
                                      : state.Failing<T>());
        }

        static JsonImportAutomaton<JsonToken> Token(Func<JsonToken, bool> predicate)
        {
            Debug.Assert(predicate != null);
            return state =>
            {
                var token = state.Input.Token;
                if (!predicate(token))
                    return state.Failing<JsonToken>();
                state.Input.Read();
                return state.Succeeding(token);
            };
        }

        public static JsonImportAutomaton<object> Null()
        {
            return Null<object>(null);
        }

        public static JsonImportAutomaton<T> Null<T>()
        {
            return Null(default(T));
        }

        public static JsonImportAutomaton<T> Null<T>(T value)
        {
            return Scalar(JsonTokenClass.Null, s => { s.Input.ReadNull(); return value; });
        }

        public static JsonImportAutomaton<string> String()
        {
            return String((s, result) => result.Succeeding(s));
        }

        public static JsonImportAutomaton<T> String<T>(Func<string, ImportState, T> converter)
        {
            return Scalar(JsonTokenClass.String, s => converter(s.Input.ReadString(), s));
        }

        public static JsonImportAutomaton<JsonNumber> Number()
        {
            return Number((s, result) => result.Succeeding(s));
        }

        public static JsonImportAutomaton<T> Number<T>(Func<JsonNumber, ImportState, T> converter)
        {
            return Scalar(JsonTokenClass.Number, s => converter(s.Input.ReadNumber(), s));
        }

        public static JsonImportAutomaton<bool> Boolean()
        {
            return Boolean((f, result) => result.Succeeding(f));
        }

        public static JsonImportAutomaton<T> Boolean<T>(Func<bool, ImportState, T> converter)
        {
            return Scalar(JsonTokenClass.Boolean, s => converter(s.Input.ReadBoolean(), s));
        }

        public static JsonImportAutomaton<JsonImportAutomaton<JsonToken>> Array()
        {
            return from content in Content()
                   from start in Token(t => t.Class == JsonTokenClass.Array)
                   select Token(t => t.Class == JsonTokenClass.EndArray);
        }

        public static JsonImportAutomaton<IEnumerable<T>> Array<T>(this JsonImportAutomaton<T> elementAutomaton)
        {
            return Array().Then<JsonImportAutomaton<JsonToken>, IEnumerable<T>>(end => state =>
            {
                var elements = new List<T>();
                while (state.Input.TokenClass != JsonTokenClass.EndArray)
                {
                    var element = elementAutomaton(state);
                    if (!state.Success)
                        return state.Failing<IEnumerable<T>>();
                    elements.Add(element);
                }
                return end.Select(_ => elements)(state).AsEnumerable();
            });
        }

        public static JsonImportAutomaton<TResult> Array<T1, T2, TResult>(
            JsonImportAutomaton<T1> arg1,
            JsonImportAutomaton<T2> arg2,
            Func<T1, T2, TResult> constructor)
        {
            if (arg1 == null) throw new ArgumentException("arg1");
            if (arg2 == null) throw new ArgumentException("arg2");
            if (constructor == null) throw new ArgumentNullException("constructor");

            return ContentThen(state =>
            {
                if (state.Input.TokenClass != JsonTokenClass.Array)
                    return state.Failing<TResult>();
                state.Input.Read();
                var v1 = arg1(state); if (!state.Success) return state.Failing<TResult>();
                var v2 = arg2(state); if (!state.Success) return state.Failing<TResult>();
                state.Input.ReadToken(JsonTokenClass.EndArray);
                return state.Succeeding(constructor(v1, v2));
            });
        }

        public static JsonImportAutomaton<TResult> Object<T1, T2, TResult>(
            KeyValuePair<string, JsonImportAutomaton<T1>> arg1,
            KeyValuePair<string, JsonImportAutomaton<T2>> arg2,
            Func<T1, T2, TResult> constructor)
        {
            return Object<T1, T2, object, TResult>(arg1, arg2, null, (v1, v2, _) => constructor(v1, v2));
        }

        public static JsonImportAutomaton<TResult> Object<T1, T2, TRest, TResult>(
            KeyValuePair<string, JsonImportAutomaton<T1>> member1,
            KeyValuePair<string, JsonImportAutomaton<T2>> member2,
            JsonImportAutomaton<TRest> others,
            Func<T1, T2, KeyValuePair<string, TRest>[], TResult> constructor)
        {
            if (string.IsNullOrEmpty(member1.Key) || member1.Value == null) throw new ArgumentException("member1");
            if (string.IsNullOrEmpty(member2.Key) || member2.Value == null) throw new ArgumentException("member2");
            if (constructor == null) throw new ArgumentNullException("constructor");

            return ContentThen(state =>
            {
                if (state.Input.TokenClass != JsonTokenClass.Object)
                    return state.Failing<TResult>();
                state.Input.Read();

                var bound = 0;
                var arg1 = default(T1);
                var arg2 = default(T2);
                var vargs = others != null ? new List<KeyValuePair<string, TRest>>() : null;

                while ((others == null || bound < 2) && state.Input.TokenClass != JsonTokenClass.EndObject)
                {
                    var member = state.Input.ReadMember();

                    if (member1.Key.Equals(member, StringComparison.OrdinalIgnoreCase))
                    {
                        arg1 = member1.Value(state);
                        if (!state.Success) return state.Failing<TResult>();
                        bound++;
                    }
                    else if (member2.Key.Equals(member, StringComparison.OrdinalIgnoreCase))
                    {
                        arg2 = member2.Value(state);
                        if (!state.Success) return state.Failing<TResult>();
                        bound++;
                    }
                    else
                    {
                        if (others != null)
                            vargs.Add(new KeyValuePair<string, TRest>(member, others(state)));
                        else
                            state.Input.Skip();
                    }
                }

                if (bound < 2)
                    return state.Failing<TResult>();

                if (others == null)
                {
                    state.Input.StepOut();
                }
                else
                {
                    while (state.Input.TokenClass != JsonTokenClass.EndObject)
                        vargs.Add(new KeyValuePair<string, TRest>(state.Input.ReadMember(), others(state)));
                    state.Input.Read();
                }

                return state.Succeeding(constructor(arg1, arg2, vargs != null ? vargs.ToArray() : null));
            });
        }

        public static JsonImportAutomaton<T> Object<T>(Func<JsonObjectImportAutomataBuilder<T>, JsonObjectImportAutomataBuilder<T>> members)
            where T : new()
        {
            var importerByName = new Dictionary<string, JsonMemberImportAutomaton<T>>(StringComparer.OrdinalIgnoreCase);
            var wildcards = new List<JsonMemberImportAutomaton<T>>();
            foreach (var member in members(JsonObjectImportAutomataBuilder<T>.Ignore()))
            {
                if (member.Key == null)
                    wildcards.Add(member.Value);
                else
                    importerByName.Add(member.Key, member.Value);
            }
            return ContentThen(state =>
            {
                if (state.Input.TokenClass != JsonTokenClass.Object)
                    return state.Failing<T>();
                state.Input.Read();
                var obj = new T();
                while (state.Input.TokenClass != JsonTokenClass.EndObject)
                {
                    // TODO Convert assertion to run-time check
                    Debug.Assert(state.Input.TokenClass == JsonTokenClass.Member);
                    var name = state.Input.Text;
                    JsonMemberImportAutomaton<T> automaton;
                    if (importerByName.TryGetValue(name, out automaton))
                    {
                        state.Input.ReadMember();
                        automaton(state, obj);
                        if (!state.Success)
                            return state.Failing<T>();
                    }
                    else
                    {
                        wildcards.Select(wildcard => wildcard(state, obj))
                                 .First(_ => state.Success);
                    }
                }
                state.Input.Read();
                return state.Succeeding(obj);
            });
        }

        public static JsonImportAutomaton<T> Ref<T>(JsonImportAutomaton<T>[] cell)
        {
            if (cell == null) throw new ArgumentNullException("cell");
            if (cell.Length != 1) throw new ArgumentException(null, "cell");
            return Ref(() => cell[0]);
        }

        public static JsonImportAutomaton<T> Ref<T>(Func<JsonImportAutomaton<T>> dereferencer)
        {
            if (dereferencer == null) throw new ArgumentNullException("dereferencer");
            var actual = new Lazy<JsonImportAutomaton<T>>(dereferencer);
            return state => actual.Value(state);
        }

        public static JsonImportAutomaton<T> Or<T>(this JsonImportAutomaton<T> left, JsonImportAutomaton<T> right)
        {
            return state =>
            {
                var value = left(state);
                return state.Success ? value : right(state);
            };
        }

        public static JsonImportAutomaton<T> OrNull<T>(this JsonImportAutomaton<T> automaton)
            where T : class
        {
            return automaton.Or(Null(default(T)));
        }

        public static JsonImportAutomaton<TResult> Select<T, TResult>(this JsonImportAutomaton<T> automaton, Func<T, TResult> selector)
        {
            return state => state.Bind(automaton(state), v => state.Succeeding(selector(v)));
        }

        public static JsonImportAutomaton<T?> OrNullable<T>(this JsonImportAutomaton<T> automaton)
            where T : struct
        {
            return automaton.Select(v => (T?) v).Or(Null<T?>());
        }

        public static JsonImportAutomaton<TResult> SelectMany<T1, T2, TResult>(
            this JsonImportAutomaton<T1> automaton,
            Func<T1, JsonImportAutomaton<T2>> selector,
            Func<T1, T2, TResult> projector)
        {
            if (automaton == null) throw new ArgumentNullException("automaton");
            if (selector == null) throw new ArgumentNullException("selector");
            if (projector == null) throw new ArgumentNullException("projector");

            return automaton.Then(t1 => selector(t1).Select(t2 => projector(t1, t2)));
        }
    }

    /*
    public sealed class ObjectMemberImportAutomaton<T>
    {
        readonly string _name;
        readonly JsonImportAutomaton<T> _automaton;

        public ObjectMemberImportAutomaton(string name, JsonImportAutomaton<T> automaton)
        {
            if (name == null) throw new ArgumentNullException("name");
            if (name.Length == 0) throw new ArgumentException(null, "name");
            if (automaton == null) throw new ArgumentNullException("automaton");
            _name = name;
            _automaton = automaton;
        }

        public ObjectMemberImportAutomaton<T, T2> Member<T2>(string name, JsonImportAutomaton<T2> automaton)
        {
            return new ObjectMemberImportAutomaton<T, T2>(Bind(v => v), name, automaton);
        }

        public JsonImportAutomaton<TResult> Bind<TResult>(Func<T, TResult> constructor)
        {
            return JsonImport.ContentThen(state =>
            {
                if (state.Input.TokenClass != JsonTokenClass.Object) return state.Failing<TResult>();
                state.Input.Read();
                if (!_name.Equals(state.Input.ReadMember(), StringComparison.OrdinalIgnoreCase))
                    return state.Failing<TResult>();
                var arg = _automaton(state);
                return state.Success 
                     ? state.Succeeding(constructor(arg)) 
                     : state.Failing<TResult>();
            });
        }
    }

    public sealed class ObjectMemberImportAutomaton<T1, T2>
    {
        readonly JsonImportAutomaton<T1> _previous;
        readonly string _name;
        readonly JsonImportAutomaton<T2> _automaton;

        public ObjectMemberImportAutomaton(JsonImportAutomaton<T1> previous, string name, JsonImportAutomaton<T2> automaton)
        {
            if (previous == null) throw new ArgumentNullException("previous");
            if (name == null) throw new ArgumentNullException("name");
            if (name.Length == 0) throw new ArgumentException(null, "name");
            if (automaton == null) throw new ArgumentNullException("automaton");
            _previous = previous;
            _name = name;
            _automaton = automaton;
        }

        public ObjectMemberImportAutomaton<T1, T2, T3> Member<T3>(string name, JsonImportAutomaton<T3> automaton)
        {
            return new ObjectMemberImportAutomaton<T1, T2, T3>(Bind(Tuple.Create), name, automaton);
        }

        public JsonImportAutomaton<TResult> Bind<TResult>(Func<T1, T2, TResult> constructor)
        {
            return state =>
            {
                var arg1 = _previous(state);
                if (!state.Success || !_name.Equals(state.Input.ReadMember(), StringComparison.OrdinalIgnoreCase))
                    return state.Failing<TResult>();
                var arg2 = _automaton(state);
                return state.Success
                     ? state.Succeeding(constructor(arg1, arg2))
                     : state.Failing<TResult>();
            };
        }
    }

    public sealed class ObjectMemberImportAutomaton<T1, T2, T3>
    {
        readonly JsonImportAutomaton<Tuple<T1, T2>> _previous;
        readonly string _name;
        readonly JsonImportAutomaton<T3> _automaton;

        public ObjectMemberImportAutomaton(JsonImportAutomaton<Tuple<T1, T2>> previous, string name, JsonImportAutomaton<T3> automaton)
        {
            if (previous == null) throw new ArgumentNullException("previous");
            if (name == null) throw new ArgumentNullException("name");
            if (name.Length == 0) throw new ArgumentException(null, "name");
            if (automaton == null) throw new ArgumentNullException("automaton");
            _previous = previous;
            _name = name;
            _automaton = automaton;
        }

        public JsonImportAutomaton<TResult> Bind<TResult>(Func<T1, T2, T3, TResult> constructor)
        {
            return state =>
            {
                var args = _previous(state);
                if (!state.Success || !_name.Equals(state.Input.ReadMember(), StringComparison.OrdinalIgnoreCase))
                    return state.Failing<TResult>();
                var arg3 = _automaton(state);
                return state.Success
                     ? state.Succeeding(constructor(args.Item1, args.Item2, arg3))
                     : state.Failing<TResult>();
            };
        }
    }
    */

    #endif // !NET_1_0 && !NET_1_1 && !NET_2_0
}